﻿using mvcForum.Core.Interfaces.Data;
using System;
using System.Linq;
using System.Linq.Expressions;

namespace mvcForum.Core.Specifications {

	public static class TopicSpecifications {

		/// <summary>
		/// A specification used when all empty topics are needed.
		/// </summary>
		public class EmptyTopic : ISpecification<Topic> {

			public EmptyTopic() { }

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					// Let's get all topics without any posts/replies!
					return x => x.PostCount == 0 && x.FlagValue == (Int32)TopicFlag.None;
				}
			}
		}

		/// <summary>
		/// This specification finds all (not deleted or quarantined) topics by a given author.
		/// </summary>
		public class AllVisibleByAuthor : ISpecification<Topic> {
			private readonly ForumUser author;

			public AllVisibleByAuthor(ForumUser author) {
				this.author = author;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return x => x.AuthorId == author.Id && (x.FlagValue & (Int32)TopicFlag.Deleted) == 0 && (x.FlagValue & (Int32)TopicFlag.Quarantined) == 0;
				}
			}
		}

		/// <summary>
		/// Given a forum and max. avarage spam score, get all announcements (that are not deleted and not quarantined).
		/// </summary>
		public class AnnouncementsNotSpam : ISpecification<Topic> {
			private readonly Forum forum;
			private readonly Int32 spamAverage;
			private readonly ForumUser user;
			private readonly Boolean moderator;

			public AnnouncementsNotSpam(Forum forum, Int32 spamAverage, ForumUser user, Boolean moderator) {
				this.forum = forum;
				this.spamAverage = spamAverage;
				this.moderator = moderator;
				this.user = user;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					// TODO:
					return x => x.ForumId == forum.Id &&
								x.TypeValue == (Int32)TopicType.Announcement &&
								(x.FlagValue & (Int32)TopicFlag.Deleted) == 0 &&
								(x.FlagValue & (Int32)TopicFlag.Quarantined) == 0 &&
								(x.SpamReporters == 0 || ((x.SpamScore / x.SpamReporters) <= this.spamAverage));
				}
			}
		}

		/// <summary>
		/// Given a forum, get all announcements (deleted and quarantined).
		/// </summary>
		public class Announcements : ISpecification<Topic> {
			private readonly Forum forum;

			public Announcements(Forum forum) {
				this.forum = forum;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return x => x.ForumId == forum.Id && x.TypeValue == (Int32)TopicType.Announcement;
				}
			}
		}

		public class StickiesNotSpam : ISpecification<Topic> {
			private readonly Forum forum;
			private readonly Int32 spamAverage;

			public StickiesNotSpam(Forum forum, Int32 spamAverage) {
				this.forum = forum;
				this.spamAverage = spamAverage;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return x => x.ForumId == forum.Id &&
							x.TypeValue == (Int32)TopicType.Sticky &&
							(x.FlagValue & (Int32)TopicFlag.Quarantined) == 0 &&
							(x.SpamReporters == 0 || ((x.SpamScore / x.SpamReporters) >= this.spamAverage));

				}
			}
		}

		public class Stickies : ISpecification<Topic> {
			private readonly Forum forum;

			public Stickies(Forum forum) {
				this.forum = forum;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return x => x.ForumId == forum.Id && x.TypeValue == (Int32)TopicType.Sticky;
				}
			}
		}

		public class RegularsNotSpam : ISpecification<Topic> {
			private readonly Forum forum;
			private readonly Int32 spamAverage;

			public RegularsNotSpam(Forum forum, Int32 spamAverage) {
				this.forum = forum;
				this.spamAverage = spamAverage;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return x => x.ForumId == forum.Id &&
								x.TypeValue == (Int32)TopicType.Regular &&
								(x.FlagValue & (Int32)TopicFlag.Quarantined) == 0 &&
								(x.SpamReporters == 0 || ((x.SpamScore / x.SpamReporters) >= this.spamAverage));
				}
			}
		}

		public class Regulars : ISpecification<Topic> {
			private readonly Forum forum;

			public Regulars(Forum forum) {
				this.forum = forum;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return x => x.ForumId == forum.Id && x.TypeValue == (Int32)TopicType.Regular;
				}
			}
		}

		public class StickiesAndRegularsNotSpam : ISpecification<Topic> {
			private readonly Forum forum;
			private readonly Int32 spamAverage;
			private readonly ForumUser user;
			private readonly Boolean moderator;

			public StickiesAndRegularsNotSpam(Forum forum, Int32 spamAverage, ForumUser user, Boolean moderator) {
				this.forum = forum;
				this.spamAverage = spamAverage;
				this.moderator = moderator;
				this.user = user;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					// TODO: Merge this into one expression?!?!
					if (this.user != null) {
						return x => x.ForumId == forum.Id &&
										(
											x.TypeValue == (Int32)TopicType.Sticky ||
											x.TypeValue == (Int32)TopicType.Regular
										) &&
										(
							// Not quarantined and not deleted!
										((x.FlagValue & (Int32)TopicFlag.Quarantined) == 0 &&
											(x.FlagValue & (Int32)TopicFlag.Deleted) == 0) ||
							// Or a moderator (will see all)
										moderator ||
							// Or the author (will see his own deleted posts).
										(x.AuthorId == this.user.Id && (x.FlagValue & (Int32)TopicFlag.Deleted) != 0)
									);
					}
					else {
						return x => x.ForumId == forum.Id &&
										(
											x.TypeValue == (Int32)TopicType.Sticky ||
											x.TypeValue == (Int32)TopicType.Regular
										) &&
										(
							// Not quarantined and not deleted!
										((x.FlagValue & (Int32)TopicFlag.Quarantined) == 0 &&
											(x.FlagValue & (Int32)TopicFlag.Deleted) == 0) ||
							// Or a moderator (will see all)
										moderator
									);
					}
				}
			}
		}

		/// <summary>
		/// Get all sticky and announcement topics in the given forum.
		/// </summary>
		public class StickiesAndRegulars : ISpecification<Topic> {
			private readonly Forum forum;

			public StickiesAndRegulars(Forum forum) {
				this.forum = forum;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					// Given the forum, get all topics where the topic flag is Sticky or Annoucement.
					return x => x.ForumId == forum.Id &&
								(x.TypeValue == (Int32)TopicType.Sticky ||
									x.TypeValue == (Int32)TopicType.Regular);
				}
			}
		}

		public class MovedTopic : ISpecification<Topic> {
			private readonly Topic topic;

			public MovedTopic(Topic topic) {
				this.topic = topic;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return t => t.OriginalTopicId == this.topic.Id;
				}
			}
		}

		public class Visible : ISpecification<Topic> {

			public Visible() { }

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return t => t.FlagValue == (Int32)TopicFlag.None ||
								t.FlagValue == (Int32)TopicFlag.Locked ||
								t.FlagValue == (Int32)TopicFlag.Moved;
				}
			}
		}

		public class ById : ISpecification<Topic> {
			private readonly Int32 id;
			
			public ById(Int32 id) {
				this.id = id;
			}

			public Expression<Func<Topic, Boolean>> IsSatisfied {
				get {
					return t => t.Id == this.id;
				}
			}
		}
	}
}